package fr.lip6.move.care.sdmaker.generator

import org.eclipse.emf.ecore.resource.Resource
import org.eclipse.xtext.generator.IFileSystemAccess
import org.eclipse.xtext.xbase.compiler.JvmModelGenerator
import fr.lip6.move.care.sdmaker.sDMaker.Diagram
import fr.lip6.move.care.sdmaker.sDMaker.Element
import fr.lip6.move.care.sdmaker.sDMaker.Call
import fr.lip6.move.care.sdmaker.sDMaker.Alt
import fr.lip6.move.care.sdmaker.sDMaker.Loop
import fr.lip6.move.care.sdmaker.sDMaker.Opt
import fr.lip6.move.care.sdmaker.sDMaker.Lifeline
import fr.lip6.move.care.sdmaker.jvmmodel.CompileResult
import fr.lip6.move.care.sdmaker.sDMaker.Declaration
import fr.lip6.move.care.sdmaker.jvmmodel.SDMakerJvmModelUtils
import java.util.ArrayList

class SDGenerator extends JvmModelGenerator {
	override doGenerate(Resource input, IFileSystemAccess fsa) {
		super.doGenerate(input, fsa)
		for(d: input.allContents.toIterable.filter(typeof(Diagram))) {			
			 fsa.generateFile(d.name + ".sd",d.compile)
			 fsa.generateFile(d.name + ".java",compileCare(d))
		}
	}
	private int i;
	
	def CharSequence compileCare(Diagram d) { 
	    var ArrayList<InterfaceData> interfacesDatas= computeAllInterfaceDatas(d)
		var CharSequence charSequenceResult= 
		'''
		package programs;
		
		import java.util.ArrayList;
		import program.*;
		import traces.*;
		
		public class «d.name» {		
		
			public static Main create(){
				int i= 0;
			    ArrayList<Actor> actors= new ArrayList<Actor>();
				
				«FOR interfaceDatas: interfacesDatas»
				ObjectClass «interfaceDatas.interfaceID»= new ObjectClass("«interfaceDatas.name»");
				«ENDFOR»
				
				«var java.util.ArrayList<CompileResult> allCompRes= new java.util.ArrayList<CompileResult>()»
				«FOR elt: d.elements»
					«var CompileResult compRes = compileEltCare(elt, interfacesDatas)»
					«SDMakerJvmModelUtils::toNullExp(allCompRes.add(compRes))»
					«compRes.charSequenceResult»
				«ENDFOR»
				
				«var String mainID= "entry_point_id"+(i=i+1)»
				ArrayList<Block> «mainID» = new ArrayList<Block>();
				«FOR elt: allCompRes»
	            	«IF elt.isAddableToEntryPoint»
	                	«mainID».add(«elt.eltID»);
	                «ENDIF»
	            «ENDFOR»
				return new Main(«mainID», actors, 200);
			} 
		
			public static void main(String[] args) {
				Main sd=create();
				boolean ok=sd.saveModel("«d.name»");
				if (ok){
					sd.genereAllTraces("«d.name»", 100);
				}
				
			}
		}
		'''
		return charSequenceResult;
	}

	
	def CharSequence compile(Diagram d) {
		'''
		«FOR elt: d.elements.filter(typeof(Lifeline))»
			«compileLifeline(elt)»
		«ENDFOR»
		
		«FOR elt: d.elements»
			«compile(elt)»
		«ENDFOR»
		'''		
	}
	
	def CharSequence compile(Element elt) {
   		switch elt {
   			
   			Call : {
   				//Call
   				compileCall(elt)
   			}
   			Alt : {
   				//Alt
   				compileAlt(elt)
   			}
   			Loop : {
   				//Loop
   				compileLoop(elt)
   			}
   			Opt : {
   				//Opt
   				compileOpt(elt)
   			}
   		}
   	}
	def CharSequence compileOpt(Opt opt) { 
		'''
		[c:opt]
			«FOR elt: opt.opts»
				«compile(elt)»
			«ENDFOR»			
		[/c]
		'''
	}

	def CharSequence compileLoop(Loop loop) { 
		'''
		[c:loop]
			«FOR elt: loop.body»
				«compile(elt)»
			«ENDFOR»			
		[/c]
		'''
	}

	def CharSequence compileAlt(Alt alt) { 
		'''
		[c:alt]
			«FOR elt: alt.conseq»
				«compile(elt)»
			«ENDFOR»
		--[else]
			«FOR elt: alt.alter»
				«compile(elt)»
			«ENDFOR»		
		[/c]
		'''
	}

	def CharSequence compileCall(Call call) { 
		'''
		«call.caller»:«call.called».«call.name»()
		«IF call.nested_calls != null»
			«FOR e: call.nested_calls»
				«compile(e)»
			«ENDFOR»
		«ENDIF»
		'''
	}

	def CharSequence compileLifeline(Lifeline lifeline) { 
		'''
		«IF lifeline.kind.equals("Actor")»
			«lifeline.name»:«lifeline.kind»
		«ELSE»
			«lifeline.name»:«lifeline.type»
		«ENDIF»
		'''
	}
	
	// debut compiler a CARE
	 
	def ArrayList<InterfaceData> computeAllInterfaceDatas(Diagram d){
	   var ArrayList<InterfaceData> allInterfaceDatas= new ArrayList<InterfaceData>(); 
	   
	   for(obj: d.eAllContents.toIterable.filter(typeof(Lifeline))){
	       if(obj.kind.compareTo("Object") == 0){
	           
	           var Boolean alreadySeenInterface= false;
	           for(interfaceName: allInterfaceDatas){
	               if(interfaceName.name.compareTo(obj.type)==0){
	                   alreadySeenInterface= true;
	               }
	           }
	           if(alreadySeenInterface==false){
	               allInterfaceDatas.add(
	                   new InterfaceData()
	                       .setName(obj.type)
	                       .setInterfaceID(obj.type.toFirstLower+"Class_id"+i)
	               );
	               i= i + 1;
	           }
	       }
	   }
	   
	   return allInterfaceDatas;
	}
	
	def InterfaceData findInterfaceID(String interfaceName, ArrayList<InterfaceData> interfacesDatas){
	    var InterfaceData foundedInterfaceDatas= null;
	    
	    for(interfaceDatas: interfacesDatas){
	        if(interfaceDatas.name.compareTo(interfaceName)==0){
	            foundedInterfaceDatas= interfaceDatas;
	        }
	    }
	    
	    return foundedInterfaceDatas;
	}
	
	def String findInterfaceIDOfActor(String actorName, ArrayList<InterfaceData> interfacesDatas){
	    var String interfaceID= null;
	    
	    for(interfaceData: interfacesDatas){
	        if(interfaceData.contains(actorName)){
	            interfaceID= interfaceData.interfaceID;
	        }
	    }
	    
	    return interfaceID;
	}
	
	def CompileResult compileEltCare(Element elt, ArrayList<InterfaceData> interfacesDatas) {
        switch elt {
            Lifeline : {
                if((elt as Lifeline).kind.compareTo("Actor")==0){
                    compileActorCare(elt, interfacesDatas)                    
                }
                else{
                    compileObjectCare(elt, interfacesDatas)
                }
            }
            Call : {
                compileCallCare(elt, interfacesDatas)
            }
            Alt : {
                compileAltCare(elt, interfacesDatas)
            }
            Loop : {
                compileLoopCare(elt, interfacesDatas)
            }
            Opt : {
                compileOptCare(elt, interfacesDatas)
            }
            default: {
                CompileResult::emptyResult
            }
        }
    }

    /* ***************************
     * Compilation des acteurs  **
     * ***************************/
    def CompileResult compileActorCare(Lifeline actor, ArrayList<InterfaceData> interfacesDatas) {
    
    var CharSequence charSequenceResult=
    '''    
                                      
    ObjectClass «actor.name»Class=new ObjectClass("«actor.name»");
    Actor «actor.name» = new Actor("«actor.name»", «actor.name»Class);   
    actors.add(«actor.name»);                                                 
    '''
    
    return new CompileResult()
                .setCharSequenceResult(charSequenceResult)
                .setEltID(actor.name)
                .setIsAddableToEntryPoint(false)
    }
    
    /* ***************************
     * Compilation des objets ****
     * ***************************/
    def CompileResult compileObjectCare(Lifeline obj, ArrayList<InterfaceData> interfacesDatas) {
        
    //In Care, Object are Actor with Interface
    var interfaceDatas= findInterfaceID(obj.type, interfacesDatas);
      
    interfaceDatas.addName(obj.name); //comme sa on saura partout retrouver
                                      //le type associé à un objet.
      
    var CharSequence charSequenceResult=
    '''
    Actor «obj.name» = new Actor("«obj.name»", «interfaceDatas.interfaceID»); 
    actors.add(«obj.name»);
    '''
        
    return new CompileResult()
                .setCharSequenceResult(charSequenceResult)
                .setEltID(obj.name)
                .setIsAddableToEntryPoint(false)
    }

    /* *************************************
     * Compilation des appels de methode ***
     * *************************************/
    def CompileResult compileCallCare(Call call, ArrayList<InterfaceData> interfacesDatas) {
    var java.util.ArrayList<CompileResult> nestedResults= new java.util.ArrayList<CompileResult>()
    var CompileResult compRes
    var callID= call.name+"_id"+(i=i+1)

    var CharSequence charSequenceResult=
    '''
    
    «FOR elt: call.nested_calls»
    «IF !(elt instanceof Declaration)»
    «SDMakerJvmModelUtils::toNullExp(compRes= compileEltCare(elt, interfacesDatas))»
    «SDMakerJvmModelUtils::toNullExp(nestedResults.add(compRes))»
    «ENDIF»
    «ENDFOR»
    
    «IF nestedResults.size() == 0»
    Call «callID» = new Call(new Statement("S" + i++,«call.caller»,new Method("«call.name»",new ArrayList<ObjectInstance>(),«findInterfaceIDOfActor(call.called, interfacesDatas)»),«call.called»));        
    «ELSEIF nestedResults.size() == 1»
    /* NESTED CALL: «callID» */
    «SDMakerJvmModelUtils::toNullExp(compRes= compileEltCare(call.nested_calls.get(0), interfacesDatas))»
    «nestedResults.get(0).charSequenceResult»
    /* FINALIZING «callID»*/
    Call «callID» = new Call(new Statement("S" + i++,«call.caller»,new Method("«call.name»",new ArrayList<ObjectInstance>(),«findInterfaceIDOfActor(call.called, interfacesDatas)»),«call.called»), «nestedResults.get(0).eltID»);
    «ELSE»
    /* NESTED CALL: «callID» */
    BlockList bloc_«callID»= new BlockList();
    «FOR nestedResult: nestedResults»
    «nestedResult.charSequenceResult»
    «ENDFOR»
    
    /* add(s) for «callID» */
    «FOR nestedResult: nestedResults»
    bloc_«callID».add(«nestedResult.eltID»);
    «ENDFOR»
    
    /* FINALIZING «callID»*/
    Call «callID» = new Call(new Statement("S" + i++,«call.caller»,new Method("«call.name»",new ArrayList<ObjectInstance>(),«findInterfaceIDOfActor(call.called, interfacesDatas)»),«call.called»), bloc_«callID»);
    «ENDIF»
    '''
    
    return new CompileResult()
                .setCharSequenceResult(charSequenceResult)
                .setEltID(callID)
                .setIsAddableToEntryPoint(true)
    }

    /* *************************************
     * Compilation des alternatives  *******
     * *************************************/
    def CompileResult compileAltCare(Alt alt, ArrayList<InterfaceData> interfacesDatas) { 
    var java.util.ArrayList<CompileResult> conseqNestedResults= new java.util.ArrayList<CompileResult>()
    var java.util.ArrayList<CompileResult> alterNestedResults= new java.util.ArrayList<CompileResult>()
    var CompileResult compRes
    var altID= "alt_id"+(i=i+1)

    var CharSequence charSequenceResult=
    '''
«««TRAITEMENT DU CONSEQUENT»»
    «FOR elt: alt.conseq»
    «IF !(elt instanceof Declaration)»
    «SDMakerJvmModelUtils::toNullExp(compRes= compileEltCare(elt, interfacesDatas))»
    «SDMakerJvmModelUtils::toNullExp(conseqNestedResults.add(compileEltCare(elt,  interfacesDatas)))»
    «ENDIF»
    «ENDFOR»
    
    «IF conseqNestedResults.size == 1»
    /* CONSEQUENT: «altID» */
    «conseqNestedResults.get(0).charSequenceResult»
    «ELSEIF conseqNestedResults.size > 1»
    /* CONSEQUENT: «altID» */
    BlockList bloc_«altID»_conseq = new BlockList();
    
    «FOR conseqNestedResult: conseqNestedResults»
    «conseqNestedResult.charSequenceResult»
    «ENDFOR»
    
    // adds for bloc_«altID»_conseq
    «FOR conseqNestedResult: conseqNestedResults»
    bloc_«altID»_conseq.add(«conseqNestedResult.eltID»);
    «ENDFOR»
    «ENDIF»

«««TRAITEMENT DE L'ALTERNANT»»
    «FOR elt: alt.alter»
    «IF !(elt instanceof Declaration)»
    «SDMakerJvmModelUtils::toNullExp(compRes= compileEltCare(elt,  interfacesDatas))»
    «SDMakerJvmModelUtils::toNullExp(alterNestedResults.add(compRes))»
    «ENDIF»
    «ENDFOR»
    
    «IF alterNestedResults.size == 1»
    /* ALTERNATIVE: «altID» */
    «alterNestedResults.get(0).charSequenceResult»
    «ELSEIF alterNestedResults.size > 1»
    /* ALTERNATIVE: «altID» */
    BlockList bloc_«altID»_alter = new BlockList();
    
    «FOR alterNestedResult: alterNestedResults»
    «alterNestedResult.charSequenceResult»
    «ENDFOR»
    
    // adds for bloc_«altID»_alter
    «FOR alterNestedResult: alterNestedResults»
    bloc_«altID»_alter.add(«alterNestedResult.eltID»);
    «ENDFOR»
    «ENDIF»

«««FINALISATION DE L'ALTERNATIVE»»
   // FINALIZING «altID»
   Alt «altID»= new Alt();
   «IF (conseqNestedResults.size == 1) && (alterNestedResults.size == 1)»
   «altID».add(«conseqNestedResults.get(0).eltID»);
   «altID».add(«alterNestedResults.get(0).eltID»);
   «ELSEIF (conseqNestedResults.size > 1) && (alterNestedResults.size == 1)»
   «altID».add(bloc_«altID»_conseq);
   «altID».add(«alterNestedResults.get(0).eltID»);   
   «ELSEIF (conseqNestedResults.size == 1) && (alterNestedResults.size > 1)»
   «altID».add(«conseqNestedResults.get(0).eltID»);
   «altID».add(bloc_«altID»_alter);
   «ELSEIF (conseqNestedResults.size > 1) && (alterNestedResults.size > 1)»
   «altID».add(bloc_«altID»_conseq);
    «altID».add(bloc_«altID»_alter);
   «ENDIF»
    '''
    
    return new CompileResult()
                .setCharSequenceResult(charSequenceResult)
                .setEltID(altID)
                .setIsAddableToEntryPoint(true)
   }




    /* *************************************
     * Compilation des boucles *************
     * *************************************/
    def CompileResult compileLoopCare(Loop loop, ArrayList<InterfaceData> interfacesDatas) {
    var java.util.ArrayList<CompileResult> nestedResults= new java.util.ArrayList<CompileResult>();
    var java.util.ArrayList<String> nestedEltIDs= new java.util.ArrayList<String>();
    var CompileResult compRes
    var loopID= "loop_id"+(i=i+1)

    var CharSequence charSequenceResult=
    '''
    
    «FOR elt: loop.body»
    «IF !(elt instanceof Declaration)»
    «SDMakerJvmModelUtils::toNullExp(compRes= compileEltCare(elt, interfacesDatas))»
    «SDMakerJvmModelUtils::toNullExp(nestedResults.add(compRes))»
    «SDMakerJvmModelUtils::toNullExp(nestedEltIDs.add(compRes.eltID))»
    «ENDIF»
    «ENDFOR»
    
    «IF nestedEltIDs.size == 1»
    /* LOOP: «loopID» */
     «nestedResults.get(0).charSequenceResult»
    Loop «loopID» = new Loop(«nestedEltIDs.get(0)»);
    «ELSEIF nestedEltIDs.size > 1»
    /* LOOP: «loopID» */
    BlockList bloc_«loopID» = new BlockList();
    «FOR nestedResult: nestedResults»
        «nestedResult.charSequenceResult»
    «ENDFOR»
    
    //add(s) for «loopID»
    «FOR eltID: nestedEltIDs»
    bloc_«loopID».add(«eltID»);
    «ENDFOR»
    
    /* FINALIZING «loopID» */
    Loop «loopID» = new Loop(bloc_«loopID»);
    «ENDIF»
    '''
    
    return new CompileResult()
                .setCharSequenceResult(charSequenceResult)
                .setEltID(loopID)
                .setIsAddableToEntryPoint(true)
    }

    /* *************************************
     * Compilation des options *************
     * *************************************/
     
     def CompileResult compileOptCare(Opt opt, ArrayList<InterfaceData> interfacesDatas) {
    var java.util.ArrayList<CompileResult> nestedResults= new java.util.ArrayList<CompileResult>();
    var java.util.ArrayList<String> nestedEltIDs= new java.util.ArrayList<String>();
    var CompileResult compRes
    var optID= "opt_id"+(i=i+1)

    var CharSequence charSequenceResult=
    '''
    
    «FOR elt: opt.opts»
    «IF !(elt instanceof Declaration)»
    «SDMakerJvmModelUtils::toNullExp(compRes= compileEltCare(elt, interfacesDatas))»
    «SDMakerJvmModelUtils::toNullExp(nestedResults.add(compRes))»
    «SDMakerJvmModelUtils::toNullExp(nestedEltIDs.add(compRes.eltID))»
    «ENDIF»
    «ENDFOR»
    
    «IF nestedEltIDs.size == 1»
    /* OPT: «optID» */
     «nestedResults.get(0).charSequenceResult»
    Opt «optID» = new Opt(«nestedEltIDs.get(0)»);
    «ELSEIF nestedEltIDs.size > 1»
    /* OPT: «optID» */
    BlockList bloc_«optID» = new BlockList();
    «FOR nestedResult: nestedResults»
    «nestedResult.charSequenceResult»
    «ENDFOR»
    
    //add(s) for bloc_«optID»
    «FOR eltID: nestedEltIDs»
    bloc_«optID».add(«eltID»);
    «ENDFOR»
    
    /* FINALIZING «optID» */
    Opt «optID» = new Opt(bloc_«optID»);
    «ENDIF»
    '''
    
    return new CompileResult()
                .setCharSequenceResult(charSequenceResult)
                .setEltID(optID)
                .setIsAddableToEntryPoint(true)
    }
}
